﻿using System;
using System.Configuration;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Threading;

namespace WAF.Server.Sessions
{
    static class IdGenerator
    {
        static byte[] sessionIDSalt;
        static ulong sessionIDCount;
        static object sessionIDCountLock = new object();
        static SHA256Managed sha = new SHA256Managed();
        static object shaLock = new object();


        static IdGenerator()
        {
            string additionalEntropy = Guid.NewGuid().ToString();
            additionalEntropy += DateTime.Now.ToString();
            additionalEntropy += Environment.TickCount.ToString();
            additionalEntropy += Environment.WorkingSet.ToString();
            additionalEntropy += Thread.CurrentThread.ManagedThreadId.ToString();
            additionalEntropy += AppDomain.CurrentDomain.Id.ToString();


            var binEntropy = new byte[additionalEntropy.Length];
            for (int i = 0; i < additionalEntropy.Length; ++i)
                binEntropy[i] = unchecked((byte)additionalEntropy[i]);

            var sha = new SHA256Managed();

            sha.TransformBlock(binEntropy, 0, binEntropy.Length, binEntropy, 0);

            var rng = new RNGCryptoServiceProvider();
            binEntropy = new byte[sha.HashSize / 8];
            rng.GetBytes(binEntropy);

            sha.TransformFinalBlock(binEntropy, 0, binEntropy.Length);
            sessionIDSalt = sha.Hash;

            sessionIDCount = 0;

        }

        public static byte[] Create()
        {
            ulong count;

            lock (sessionIDCountLock)
            {
                count = sessionIDCount++;
            }

            // Put some additional entropy in the count to strenghten the SessionID
            const int entropyLength = 16;
            var binEntropy = new byte[entropyLength + sessionIDSalt.Length];
            unchecked
            {
                binEntropy[0] = (byte)(count & 0xFF);
                binEntropy[1] = (byte)((count >> 8) & 0xFF);
                binEntropy[2] = (byte)((count >> 16) & 0xFF);
                binEntropy[3] = (byte)((count >> 24) & 0xFF);
                binEntropy[4] = (byte)((count >> 32) & 0xFF);
                binEntropy[5] = (byte)((count >> 40) & 0xFF);
                binEntropy[6] = (byte)((count >> 48) & 0xFF);
                binEntropy[7] = (byte)((count >> 56) & 0xFF);

                uint ticks = (uint)Environment.TickCount;
                ticks ^= (uint) count; // XOR with count to get more sessionIDCount dependend bits 

                binEntropy[8] = (byte)(ticks & 0xFF);
                binEntropy[9] = (byte)((ticks >> 8) & 0xFF);
                binEntropy[10] = (byte)((ticks >> 16) & 0xFF);
                binEntropy[11] = (byte)((ticks >> 24) & 0xFF);

                uint threadID = (uint)Thread.CurrentThread.ManagedThreadId;
                threadID ^= (uint) count; // XOR with count to get more sessionIDCount dependend bits

                binEntropy[12] = (byte)(threadID & 0xFF);
                binEntropy[13] = (byte)((threadID >> 8) & 0xFF);
                binEntropy[14] = (byte)((threadID >> 16) & 0xFF);
                binEntropy[15] = (byte)((threadID >> 24) & 0xFF);
            }

            for (int i = 0; i < sessionIDSalt.Length; ++i)
                binEntropy[i + entropyLength] = sessionIDSalt[i];

            lock (shaLock)
            {
                return sha.ComputeHash(binEntropy);
            }
        }
    }

}